package auth

import (
	"errors"
	jwt "github.com/gogf/gf-jwt"
	"github.com/gogf/gf/database/gdb"
	"github.com/gogf/gf/frame/g"
	"github.com/gogf/gf/net/ghttp"
	"github.com/gogf/gf/os/glog"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/util/gvalid"
	"net/http"
	"time"
)

var (
	// The underlying JWT middleware.
	GfJWTMiddleware *jwt.GfJWTMiddleware
	// Customized login parameter validation rules.
	ValidationRules = g.Map{
		"userid":   "required",
		"password": "required",
	}
)

// Initialization function,
// rewrite this function to customized your own JWT settings.
func init() {
	authMiddleware, err := jwt.New(&jwt.GfJWTMiddleware{
		Realm:           "test zone",
		Key:             []byte("secret key"),
		Timeout:         time.Hour * 12,
		MaxRefresh:      time.Hour * 24,
		IdentityKey:     "id",
		TokenLookup:     "header: Authorization, query: token, cookie: jwt",
		TokenHeadName:   "Bearer",
		TimeFunc:        time.Now,
		Authenticator:   Authenticator,
		LoginResponse:   LoginResponse,
		LogoutResponse:  LogoutResponse,
		RefreshResponse: RefreshResponse,
		Unauthorized:    Unauthorized,
		IdentityHandler: IdentityHandler,
		PayloadFunc:     PayloadFunc,
	})
	if err != nil {
		glog.Fatal("JWT Error:" + err.Error())
	}
	GfJWTMiddleware = authMiddleware
}

// PayloadFunc is a callback function that will be called during login.
// Using this function it is possible to add additional payload data to the webtoken.
// The data is then made available during requests via c.GET("JWT_PAYLOAD").
// Note that the payload is not encrypted.
// The attributes mentioned on jwt.io can't be used as keys for the map.
// Optional, by default no additional data will be set.
func PayloadFunc(data interface{}) jwt.MapClaims {
	claims := jwt.MapClaims{}
	params := data.(map[string]interface{})
	if len(params) > 0 {
		for k, v := range params {
			claims[k] = v
		}
	}
	return claims
}

// IdentityHandler sets the identity for JWT.
func IdentityHandler(r *ghttp.Request) interface{} {
	claims := jwt.ExtractClaims(r)
	return claims["id"]
}

// Unauthorized is used to define customized Unauthorized callback function.
func Unauthorized(r *ghttp.Request, code int, message string) {
	r.Response.WriteJson(g.Map{
		"code": code,
		"msg":  message,
	})
	r.ExitAll()
}

// LoginResponse is used to define customized login-successful callback function.
func LoginResponse(r *ghttp.Request, code int, token string, expire time.Time) {
	data := r.GetMap()
	r.Response.WriteJson(g.Map{
		"code":   http.StatusOK,
		"token":  token,
		"expire": expire.Format(time.RFC3339),
		"userid": data["userid"],
	})
	r.ExitAll()
}

func LogoutResponse(r *ghttp.Request, code int) {
	r.Response.WriteJson(g.Map{
		"code":    code,
		"message": "success",
	})
	r.ExitAll()
}

// RefreshResponse is used to get a new token no matter current token is expired or not.
func RefreshResponse(r *ghttp.Request, code int, token string, expire time.Time) {
	r.Response.WriteJson(g.Map{
		"code":   http.StatusOK,
		"token":  token,
		"expire": expire.Format(time.RFC3339),
	})
	r.ExitAll()
}

// Authenticator is used to validate login parameters.
// It must return user data as user identifier, it will be stored in Claim Array.
// Check Error (e) to determine the appropriate Error message.
func Authenticator(r *ghttp.Request) (interface{}, error) {
	data := r.GetMap()
	ip := r.GetClientIp()
	agent := r.UserAgent()

	db := g.DB(g.Cfg().GetString("custom.db_name"))

	if e := gvalid.CheckMap(r.Context(), data, ValidationRules); e != nil {
		//
		db.Model("t_login_log").Data(gdb.Map{"userid": data["userid"], "type": 0, "ip": ip, "agent": agent, "remark": "ErrFailedAuthentication"}).Insert()
		return "", jwt.ErrFailedAuthentication
	}
	count, err := db.Model("t_user").Where("status=1 and userid=? and pwd=?", data["userid"], data["password"]).Count()
	if err != nil {
		db.Model("t_login_log").Data(gdb.Map{"userid": data["userid"], "type": 0, "ip": ip, "agent": agent, "remark": err.Error()}).Insert()
		return nil, errors.New("System Error")
	}
	if count > 0 {
		db.Model("t_login_log").Data(gdb.Map{"userid": data["userid"], "type": 1, "ip": ip, "agent": agent, "remark": "success"}).Insert()
		//更新user
		db.Model("t_user").Data(gdb.Map{"login_ip": ip, "login_info": agent, "login_time": gtime.Now().Format("Y-m-d H:i:s.u")}).Where("userid=?", data["userid"]).Update()
		return g.Map{
			"userid": data["userid"],
			"id":     data["userid"],
		}, nil
	} else {
		db.Model("t_login_log").Data(gdb.Map{"userid": data["userid"], "type": 0, "ip": ip, "agent": agent, "remark": "Error password or userid"}).Insert()
	}
	//return nil, jwt.ErrFailedAuthentication
	return nil, errors.New("错误的账号密码")
}

// authHook is the HOOK function implements JWT logistics.
func MiddlewareAuth(r *ghttp.Request) {
	GfJWTMiddleware.MiddlewareFunc()(r)
	r.Middleware.Next()
}

func MiddlewareCORS(r *ghttp.Request) {
	r.Response.CORSDefault()
	r.Middleware.Next()
}
